﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    public class DelegateAndEvent
    {
        //委托是一个类，它定义了方法的类型，使得可以将方法当作另一个方法的参数来进行传递，
        //这种将方法动态地赋给参数的做法，可以避免在程序中大量使用If-Else(Switch)语句，同时使得程序具有更好的可扩展性。
        public void DelegateTestFunc()
        {
            DelegateFunc1();
            EventTestClass ec = new EventTestClass();
            //ec.ce = (int a) => { Console.WriteLine(a); }; //委托声明成public后可以在外部调用时随意赋值，破坏了封装性
            
            //xx.checkEvent只能出现在 += 或 -= 的左边(从类型“Delegate.GreetingManager”中使用时除外)
            //ec.checkEvent=(int a) => { Console.WriteLine(a); };   //编译不通过
            ec.checkEvent += (int a) => { Console.WriteLine(a); };    
            ec.EventTestFunc();
            ec.ExecEvent(50);   //事件只能在定义事件的类的内部进行调用

            //热水器DEMO
            HeatObersever ho = new HeatObersever();
            ho.ExecHeat();

            Console.WriteLine();
            HeatSObersever hso = new HeatSObersever();
            hso.ExecHeaterS();

        }

        #region 委托的声明方式
        delegate void CheckDelegate(int number);     //相当于声明了一个类，不实例化是没法使用的
        delegate T2 GenericDelegate<T1, T2>(T1 t1);  //泛型委托，T1是参数类型，T2是返回值类型
        private void DelegateFunc1()
        {
            CheckDelegate cd = new CheckDelegate(CheckOdd);         //实例化并注册方法
            CheckDelegate cd2 = CheckOdd;                           //.net 2.0以后直接注册方法

            CheckDelegate cd3 = new CheckDelegate(delegate(int i)   //匿名委托
            {
                Console.WriteLine("this is {0}", i);
            });
            CheckDelegate cd4 = delegate(int j)                     //匿名委托
            {
                Console.WriteLine("this is {0}", j);
            };

            CheckDelegate cd5 = (int x) =>                          //Lambda表达式写法
            {
                Console.WriteLine("this is {0}", x);
            };

            GenericDelegate<int, string> gd = (int a) =>
            {
                return "this is " + a;
            };

            //Action是系统封装好的委托，只能接收参数不能有返回值,最多16个参数
            Action<int> a1=(int input) =>
                {
                    Console.WriteLine("this is {0}", input);
                };

            //Func是系统封装好的委托，能接收参数并有返回值
            Func<int, string> a2 = (int input) =>
                {
                    return "this is " + input;
                };

            //Predicate只能接收一个参数，返回bool，引用一个判断条件的方法，然后在集合的find中使用
            Predicate<int> p1 = (int input) =>
                { return input > 50; };
            List<int> li = new List<int>{ 10, 50, 90, 100 };
            Console.WriteLine("大于50的第一个元素为" + li.Find(p1));//要全部找出的话就遍历

            cd(5);
            cd2(10);
            cd3(15);
            cd4(20);
            cd5(25);
            a1(123);
            Console.WriteLine(a2(456));
            Console.WriteLine(gd(30));
        }
        private void CheckOdd(int num)
        {
            if (num % 2 == 1)
                Console.WriteLine("{0}是奇数", num);
            else
                Console.WriteLine("{0}是偶数", num);
        }
        #endregion

    }

    #region 多播委托
    class MultiCastDelegate
    {
        private delegate void hkHandler(string msg);
        private delegate int hkHandler2();
        private delegate void hkHandler3(out int a);

        public MultiCastDelegate()
        {
            hkHandler hkd = new hkHandler(testFunc1);
            hkd += testFunc2;   //绑定第二个方法
            hkd("这是通过多播委托调用的");

            //如果返回类型不是void，那返回值只能拿到委托列表里最后一个方法的返回值
            hkHandler2 hkd2 = new hkHandler2(testFunc3);
            hkd2 += testFunc4;
            Console.WriteLine(hkd2());

            //out参数也是如此，out值为最后一个方法里的赋值结果
            hkHandler3 hkd3 = new hkHandler3(testFunc5);
            hkd3 += testFunc6;
            int result = 0;
            hkd3(out result);
            Console.WriteLine(result);
        }

        private void testFunc1(string msg)
        {
            Console.WriteLine("tf1" + msg);
        }
        private void testFunc2(string msg)
        {
            Console.WriteLine("tf2" + msg);
        }

        private int testFunc3()
        {
            return 3;
        }
        private int testFunc4()
        {
            return 4;
        }

        private void testFunc5(out int i)
        {
            i = 5;
        }
        private void testFunc6(out int i)
        {
            i = 6;
        }
    }

    #endregion

    #region 事件
    public class EventTestClass
    { 
        public delegate void CheckEvent(int a);
        private CheckEvent ce;   //声明成private，外部无法调用。声明成public，外部可以随意赋值，破坏了封装性
        //事件的定义方式(和委托很相似)event <委托类型> 事件名 (注意是个委托类型)
        //事件是对委托的封装，就如同c#类中属性对字段的封装一样，其封装后可以在委托上封装更复杂的逻辑
        //在类的内部，不管你声明它是public还是protected，它总是private的。
        //在类的外部，注册“+=”和注销“-=”的访问限定符与你在声明事件时使用的访问符相同。
        public event CheckEvent checkEvent;
        public void EventTestFunc()
        {
            checkEvent = new CheckEvent(GetNum);    //将方法注册到事件上
            //checkEvent += new CheckEvent(GetNum);   
            checkEvent += GetNum;
            checkEvent -= GetNum;                   //解除注册

            ce = GetNum;
            checkEvent(10);
            ce(15);
        }
        private void GetNum(int a)
        {
            Console.WriteLine("this is " + a);
        }
        public void ExecEvent(int i)
        {
            checkEvent(i);
        }
    }

    #endregion

    #region Observer模式Demo-热水器
    public class Heater
    {
        private int temperature = 0;
        public delegate void BoilWaterDelegate(int t);
        public event BoilWaterDelegate bwEvent;

        public void BoilWater() 
        {
            for (int i = 0; i < 100; i++)
            {
                temperature = i;
                if (temperature > 95)
                {
                    bwEvent(temperature);
                }
            }
            
        }
    }

    public class Alarm
    {
        public void TAlarm(int t)
        {
            Console.WriteLine("水快开啦！！！当前水温{0}",t);
        }
    }

    public class Dispaly
    {
        public void TDisplay(int t)
        {
            Console.WriteLine("水100°啦！！！当前水温{0}", t);
        }
    }

    public class HeatObersever
    {
        public void ExecHeat()
        {
            Heater ht = new Heater();
            Alarm al = new Alarm();
            Dispaly di = new Dispaly();

            ht.bwEvent += al.TAlarm;
            ht.bwEvent += di.TDisplay;
            ht.BoilWater();
        }
    }

    #endregion

    #region 用符合.net规范的方式写热水器DEMO
    /*
     * 为什么.Net Framework 中的事件模型和上面的不同？为什么有很多的EventArgs参数？
     * 在回答上面的问题之前，我们先搞懂 .Net Framework的编码规范：
     * 委托类型的名称都应该以EventHandler结束。
     * 委托的原型定义：有一个void返回值，并接受两个输入参数：一个Object 类型，一个 EventArgs类型(或继承自EventArgs)。
     * 事件的命名为 委托去掉 EventHandler之后剩余的部分。
     * 继承自EventArgs的类型应该以EventArgs结尾。
    */
    public class Heater_S
    {
        private int temperature = 0;
        public string type = "Rinnei";
        //声明委托
        public delegate void BoiledEventHandler(object sender, BoiledEventArgs e);
        public event BoiledEventHandler BoiledEvnet;

        // 定义BoiledEventArgs类，传递给Observer所感兴趣的信息
        public class BoiledEventArgs : EventArgs
        {
            public readonly int temperature;
            public BoiledEventArgs(int t)
            {
                this.temperature = t;
            }
        }

        // 可以供继承自 Heater 的类重写，以便继承类拒绝其他对象对它的监视
        protected virtual void OnBoiled(BoiledEventArgs e)
        {
            if (BoiledEvnet != null)
                BoiledEvnet(this, e);
        }

        public void BoilWater()
        {
            for (int i = 1; i <= 100; i++)
            {
                temperature=i;
                if (temperature > 95)
                {
                    BoiledEventArgs e = new BoiledEventArgs(temperature);
                    OnBoiled(e);
                }
            }
        }
    }

    public class Alarm_S
    {
        public void TAlarm(Object sender, Heater_S.BoiledEventArgs e)
        {
            //将热水器的引用传给警报器的方法，就可以在方法中直接访问热水器了。避免委托要传很多参数
            Heater_S hs = (Heater_S)sender;
            Console.WriteLine("{0}牌热水器:滴滴滴",hs.type);
        }
    }

    public class Display_S
    {
        public void TDisplay(Object sender, Heater_S.BoiledEventArgs e)
        {
            Heater_S hs = (Heater_S)sender;
            Console.WriteLine("{0}牌热水器:温度为{1}",hs.type,e.temperature);
        }
    }

    public class HeatSObersever
    {
        public void ExecHeaterS()
        {
            Heater_S hs = new Heater_S();
            Alarm_S als = new Alarm_S();
            Display_S dis = new Display_S();

            hs.BoiledEvnet += als.TAlarm;
            hs.BoiledEvnet += dis.TDisplay;

            hs.BoilWater();
        }
    }

    #endregion
}
